package skymeta

import (
	"context"

	"gitee.com/simonxie979/skymeta/protocol"
	"gitee.com/simonxie979/skymeta/uuid"
)

type IService interface {
	Init(ctx context.Context, serviceID uint64) error
	Shutdown()
	GetServiceID() uint64
	GetServiceName() string
	OnMessage(session, source uint64, msgName string, msgBody []byte)
}

// container is container of service.
type container struct {
	ctx    context.Context
	cancel context.CancelFunc

	sessionID   uint64
	serviceID   uint64
	serviceName string

	instance IService
	inPipe   chan *protocol.SSMessage
}

// GetSessionID Get session id of the session to which the service belongs in the container.
func (c *container) GetSessionID() uint64 {
	return c.sessionID
}

// GetServiceID Get service id of service in the container.
func (c *container) GetServiceID() uint64 {
	return c.serviceID
}

// GetServiceName Get service name of service in the container.
func (c *container) GetServiceName() string {
	return c.serviceName
}

// IsLocal Return the container is launched in local node.
//  true: launched by local node
//  false: launched by remote node
func (c *container) IsLocal() bool {
	return c.sessionID == 0
}

// Shutdown Shutdown the container and service in container.
func (c *container) Shutdown() {
	c.cancel()
	c.instance.Shutdown()
}

// invoke Safe calling function of message handle according to message.
func (c *container) invoke(msg *protocol.SSMessage) {
	defer func() {
		if err := recover(); err != nil {
			logger.Errorf("Container", "%s[%X] message handle failure. err: %v", c.serviceName, c.serviceID, err)
		}
	}()

	c.instance.OnMessage(msg.GetSequence(), msg.GetSource(), msg.GetName(), msg.GetBody())
}

// dispatch Dispatches messages for sesvice in container.
func (c *container) dispatch() {
	for {
		select {
		case <-c.ctx.Done():
			return
		case msg := <-c.inPipe:
			c.invoke(msg)
		}
	}
}

// newLocalContainer Create a local service container.
func newLocalContainer(srv IService) (*container, error) {
	c := new(container)
	c.ctx, c.cancel = context.WithCancel(g_Context)
	c.sessionID = 0
	c.instance = srv
	c.serviceID = uuid.GetGuid()
	c.inPipe = make(chan *protocol.SSMessage, 10000)

	if err := srv.Init(c.ctx, c.serviceID); err != nil {
		return nil, err
	}

	c.serviceName = srv.GetServiceName()

	go c.dispatch()

	return c, nil
}

// newRemoteContainer Create a remote service container.
func newRemoteContainer(sessionID, serviceID uint64, serviceName string) *container {
	c := new(container)
	c.sessionID = sessionID
	c.serviceID = serviceID
	c.serviceName = serviceName

	return c
}
